iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0
自我挑戰組

攜手 AI 從零開始打造一款 Flutter 應用程式系列 第 28

Day 28: 保護你的金鑰 - 使用環境變數管理 API Key

  • 分享至 

  • xImage
  •  

前言

大家好!在 Day 27,我們成功地為 Firestore 資料庫加上了安全規則,保護了使用者的個人資料隱私。至此,我們後端的安全性已經得到了保障。然而,我們的程式碼中還潛藏著一個明顯的安全風險。

回顧 gemini_service.dart 檔案,我們的 Gemini API 金鑰是這樣寫的:
final String _apiKey = 'AIzaSyB...';

將 API Key 這樣的「密鑰 (Secret)」直接寫在程式碼中,被稱為「硬編碼 (Hardcoding)」。這是一個極其危險的壞習慣。如果我們將專案上傳到公開的 GitHub 儲存庫,任何人都能看到我們的金鑰,並盜用我們的 API 額度,可能導致預期外的鉅額帳單。即使是私有儲存庫,這也不是一個專業的管理方式。

今天,我們將學習如何使用 flutter_dotenv 套件,將這個敏感的金鑰從程式碼中抽離,儲存在一個獨立的、被版本控制忽略的檔案中。

Step 1: 加入 flutter_dotenv 套件並設定

  1. 此指令會自動抓取最新穩定版本,並將其加入 pubspec.yaml 的依賴項目中。:
flutter pub add flutter_dotenv
  1. 建立 .env 檔案:在專案的根目錄(與 pubspec.yaml 同一層),手動建立一個新檔案,並將其命名為 .env

  2. 儲存你的金鑰:打開 .env 檔案,以 KEY=VALUE 的格式儲存你的金鑰。

# .env
GEMINI_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
  1. 忽略 .env 檔案 (至關重要!):我們必須告訴 Git,永遠不要追蹤這個檔案。打開專案根目錄的 .gitignore 檔案,在檔案的最底部加上新的一行:
# .gitignore

# ... 其他忽略規則 ...

# 忽略環境變數檔案
.env
  1. pubspec.yaml 中宣告資源:我們需要讓 Flutter 在打包 App 時,將 .env 檔案包含進去。打開 pubspec.yaml,在 flutter: 區塊下的 assets: 中加入 .env 檔案的路徑。
flutter:
  # ...
  assets:
    - .env # 新增這一行
    - assets/icon/
    - assets/splash/

Step 2: 在 App 啟動時載入環境變數

.env 檔案本身不會被自動讀取,我們需要在 App 啟動時手動載入它。最佳的時機點就在 main.dartmain 函式中。

// lib/main.dart
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; // 1. 導入套件
import 'firebase_options.dart';
// ... 其他 import ...

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 2. 在 Firebase 初始化之前,載入 .env 檔案
  await dotenv.load(fileName: ".env");

  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const MyApp());
}

Step 3: 從 GeminiService 中安全地讀取金鑰

萬事俱備,現在我們可以回到 gemini_service.dart,移除硬編碼的金鑰,改為從 dotenv 中讀取。

// lib/services/gemini_service.dart
import 'dart:convert';
import 'package:google_generative_ai/google_generative_ai.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; // 1. 導入套件
import 'package:snapsaver/models/transction.dart';

class GeminiService {
  // 2. 從環境變數中讀取 API 金鑰
  final String _apiKey = dotenv.env['GEMINI_API_KEY'] ?? '';

  Future<Map<String, dynamic>?> analyzeReceiptText(String ocrText) async {
    // 3. 我們在**每個需要呼叫 API 的方法**中,都先檢查 _apiKey 是否為空...
    if (_apiKey.isEmpty) {
      return {'error': 'Gemini API 金鑰未設定,請檢查您的 .env 檔案。'};
    }
    // ... 其他程式碼不變,apiKey 參數會自動使用 _apiKey 欄位 ...
  }

  Future<String?> getExpenseAnalysis(List<Transaction> transactions) async {
    if (_apiKey.isEmpty) {
      return 'Gemini API 金鑰未設定,請檢查您的 .env 檔案。';
    }
    // ... 其他程式碼不變 ...
  }
}
  • _apiKey 的值不再是寫死的字串,而是透過 dotenv.env['GEMINI_API_KEY'] 從載入的環境變數中讀取。
  • 我們在每個方法的一開始都檢查 _apiKey 是否為空,如果開發者忘記設定 .env 檔案,App 會回傳一個清晰的錯誤訊息,而不是直接崩潰或發送無效請求。

Step 4: 驗證功能

必須完全停止並重新啟動你的 App (Cold Restart),因為 .env 檔案只會在 main 函式執行時被載入一次。Hot Reload 或 Hot Restart 是無效的。

重新啟動後,去測試「智慧掃描」和「智慧分析」功能,它們應該要能像之前一樣正常運作。這代表 App 已經成功地從 .env 檔案中讀取到了你的金鑰。

也可以在終端機執行 git status,你會發現 .env 檔案完全沒有出現在待提交的清單中,代表它被 .gitignore 成功忽略了!

鐵人賽最終章預告
至此,我們已經完成了所有核心功能的開發、重構、優化與安全加固。我們的 App 無論在功能、架構還是安全性上,都已經達到了一個相當完整的水平。

在鐵人賽的最後兩天,我們將放慢撰寫新程式碼的腳步,轉而進行回顧與總結。

  • Day 29 (明天): 專案總結與回顧:我們將一起回顧這 29 天的開發旅程,盤點我們從零到一打造「省錢拍拍」的過程中,克服了哪些挑戰、掌握了哪些關鍵技術。
  • Day 30: 最終章 - 感想與未來展望:為這次的鐵人賽畫下句點,分享我個人的開發心得,並展望「省錢拍拍」這個專案未來還可以添加的更多有趣功能。

今日結語

今天我們補上了 App 安全性的最後一塊拼圖。我們學會了:

  1. 為什麼不能將密鑰硬編碼在程式碼中。
  2. 如何使用 flutter_dotenv 套件來管理 API 金鑰。
  3. 設定 .gitignore 以確保敏感檔案不會被納入版本控制。

我們的「省錢拍拍」App 現在不僅功能強大、安全可靠,更遵循了專業的開發實踐。明天,讓我們一起來回顧這趟精彩的旅程吧!


上一篇
Day 27: 為資料庫上鎖 - 撰寫 Firestore 安全規則
下一篇
Day 29: 專案總回顧 - 從零到一的 Flutter x AI 之旅
系列文
攜手 AI 從零開始打造一款 Flutter 應用程式29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言